#region License, Terms and Conditions
//
// Prawks - Copyright (c) 2007 Atif Aziz. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 2.1 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
//
#endregion

namespace Prawks.CodeProvisioning
{
    using System;
    using System.Reflection;
    using System.Runtime.Serialization;

    /// <summary>
    /// Provides serialization support so that transient and dynamic types can be 
    /// persisted as well as passed across remoting boundaries by-value.
    /// </summary>
    /// <remarks>
    /// This class is deliberately not marked as serializable. It is only designed to 
    /// hold references to other members during serialization and should not itself be 
    /// serializable. <see cref="DynamicSerializationSurrogate"/> and types that
    /// derived from it are also not true serialization surrogates in the sense of
    /// the .NET Framework. That is, they do not implement 
    /// <see cref="ISerializationSurrogate"/> although they perform the exact same
    /// set of actions. The main difference is that a normal serizalization surrogate
    /// is designed to be registered with a surrogate selector so that serialization
    /// can be hooked into from the outside. <see cref="DynamicSerializationSurrogate"/>
    /// objects, on the other hand, are hooked into the serialization of their real
    /// object from the inside, during dynamic type creation. This also make it much
    /// more transparent for the user of the dynamic type rather than to burden them
    /// with having to register surrogates consiously.
    /// </remarks>
    
    public abstract class DynamicSerializationSurrogate : ISerializable, IObjectReference
    {
        private const string _baseTypeName = "__BaseTypeName";
        private const string _baseAssemblyName = "__BaseAssemblyName";

        private readonly object _realObject;
        private readonly SerializationInfo _info;
        private readonly Type _realType;

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicSerializationSurrogate"/> class.
        /// </summary>
        /// <param name="realObject">
        /// The real object for which this instance is to provide serialization.
        /// </param>

        protected DynamicSerializationSurrogate(object realObject)
        {
            _realObject = realObject;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicSerializationSurrogate"/> class 
        /// with serialized data.
        /// </summary>
        /// <param name="info">
        /// The object that holds the serialized object data.
        /// </param>
        /// <param name="context">
        /// The contextual information about the source or destination. 
        /// </param>
        
        protected DynamicSerializationSurrogate(SerializationInfo info, StreamingContext context) 
        {
            //
            // Get the baked version of the base type that will be deserialized
            // and returned as the real object later.
            //

            Assembly realBaseAssembly = Assembly.Load(info.GetString(_baseAssemblyName));
            Type realBaseType = realBaseAssembly.GetType(info.GetString(_baseTypeName));
            _realType = DynamicTypeImpl.GetImplType(realBaseType);

            //
            // Copy the serialization data. Note that the FormatterConverter 
            // plays no role here whatsoever but had to be supplied because
            // SerializationInfo's constructor expect a non-null value.
            //

            _info = new SerializationInfo(_realType, new FormatterConverter());
            
            SerializationInfoEnumerator e = info.GetEnumerator();
            while (e.MoveNext())
                _info.AddValue(e.Name, e.Value, e.ObjectType);
        }
        
        /// <summary>
        /// Gets serialization information with all of the data needed to reinstantiate this assembly.
        /// </summary>
        /// <param name="info">
        /// The object to be populated with serialization information. 
        /// </param>
        /// <param name="context">
        /// The destination context of the serialization. 
        /// </param>

        public virtual void GetObjectData(SerializationInfo info, StreamingContext context) 
        {
            //
            // Record the type and the assembly of the object's base type. This 
            // information will be vital upon deserialization in the new context
            // in order to place a call to DynamicTypeImpl.GetImplType.
            //

            Type baseType = _realObject.GetType().BaseType;

            info.AddValue(_baseAssemblyName, baseType.Assembly.FullName);
            info.AddValue(_baseTypeName, baseType);

            //
            // Change the type being serialized over to this object.
            //

            info.SetType(GetType());
        }

        /// <summary>
        /// Returns the real object that should be deserialized, rather than the object 
        /// that the serialized stream specifies.
        /// </summary>
        /// <param name="context">
        /// The context from which the current object is deserialized. 
        /// </param>
        /// <returns>
        /// Returns the actual object that is put into the graph.
        /// </returns>

        public abstract object GetRealObject(StreamingContext context);

        /// <summary>
        /// Gets the real object instance during serialization.
        /// </summary>
        
        protected object RealObject 
        {
            get { return _realObject; }
        }

        /// <summary>
        /// Gets the type of the real object to be deserialized.
        /// </summary>

        protected Type RealType
        {
            get { return _realType; }
        }

        /// <summary>
        /// Gets the object that holds the serialized object data.
        /// </summary>

        protected SerializationInfo Info
        {
            get { return _info; }
        }
    }

    /// <summary>
    /// Provides serialization support so that transient and dynamic types that require
    /// custom serizalization via <see cref="ISerializable"/> can be persisted as
    /// well as passed across remoting boundaries by-value.
    /// </summary>

    public class DynamicCustomSerializationSurrogate : DynamicSerializationSurrogate
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicCustomSerializationSurrogate"/> class.
        /// </summary>
        /// <param name="realObject">
        /// The real object for which this instance is to provide serialization.
        /// </param>

        public DynamicCustomSerializationSurrogate(object realObject) : 
            base(realObject) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicCustomSerializationSurrogate"/> class 
        /// with serialized data.
        /// </summary>
        /// <param name="info">
        /// The object that holds the serialized object data.
        /// </param>
        /// <param name="context">
        /// The contextual information about the source or destination. 
        /// </param>

        protected DynamicCustomSerializationSurrogate(SerializationInfo info, StreamingContext context) :
            base(info, context) {}
        
        /// <summary>
        /// Returns the real object that should be deserialized, rather than the object 
        /// that the serialized stream specifies.
        /// </summary>
        /// <param name="context">
        /// The context from which the current object is deserialized. 
        /// </param>
        /// <returns>
        /// Returns the actual object that is put into the graph.
        /// </returns>

        public override object GetRealObject(StreamingContext context)
        {
            //
            // Create the real object and load it with the data supplied for
            // deserialization.
            //

            object realObject = FormatterServices.GetUninitializedObject(RealType);

            ObjectManager om = new ObjectManager(null, context);
            om.RegisterObject(realObject, 1, Info);
            om.DoFixups();

            return om.GetObject(1);
        }
    }

    /// <summary>
    /// Provides serialization support so that transient and dynamic types that are
    /// tagged with the Serializable attribute can be persisted as
    /// well as passed across remoting boundaries by-value.
    /// </summary>

    public class DynamicMemberSerializationSurrogate : DynamicSerializationSurrogate
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicMemberSerializationSurrogate"/> class.
        /// </summary>
        /// <param name="realObject">
        /// The real object for which this instance is to provide serialization.
        /// </param>

        public DynamicMemberSerializationSurrogate(object realObject) : 
            base(realObject) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicMemberSerializationSurrogate"/> class 
        /// with serialized data.
        /// </summary>
        /// <param name="info">
        /// The object that holds the serialized object data.
        /// </param>
        /// <param name="context">
        /// The contextual information about the source or destination. 
        /// </param>

        protected DynamicMemberSerializationSurrogate(SerializationInfo info, StreamingContext context) :
            base(info, context) {}

        /// <summary>
        /// Gets serialization information with all of the data needed to reinstantiate this assembly.
        /// </summary>
        /// <param name="info">
        /// The object to be populated with serialization information. 
        /// </param>
        /// <param name="context">
        /// The destination context of the serialization. 
        /// </param>

        public override void GetObjectData(SerializationInfo info, StreamingContext context) 
        {
            //
            // Use FormatterServices to get the serializable members and then
            // write out their data.
            //

            MemberInfo[] members = FormatterServices.GetSerializableMembers(RealObject.GetType(), context);
            object[] datas = FormatterServices.GetObjectData(RealObject, members);

            int i = 0;

            foreach (MemberInfo member in members)
            {
                if (member.MemberType == MemberTypes.Field)
                {
                    info.AddValue(member.Name, datas[i], ((FieldInfo) member).FieldType);
                }
                else if (member.MemberType == MemberTypes.Property)
                {
                    info.AddValue(member.Name, datas[i], ((PropertyInfo) member).PropertyType);
                }
                else
                {
                    throw new SerializationException(
                        string.Format("Cannot serialize member {0} of {1} because it is neither a field nor a property.", 
                        member.Name, member.DeclaringType.FullName));
                }

                i++;
            }

            base.GetObjectData(info, context);
        }

        /// <summary>
        /// Returns the real object that should be deserialized, rather than the object 
        /// that the serialized stream specifies.
        /// </summary>
        /// <param name="context">
        /// The context from which the current object is deserialized. 
        /// </param>
        /// <returns>
        /// Returns the actual object that is put into the graph.
        /// </returns>

        public override object GetRealObject(StreamingContext context)
        {
            //
            // Create the real object and then use FormatterServices to get
            // the deserializable members.
            //

            object realObject = FormatterServices.GetUninitializedObject(RealType);
            MemberInfo[] members = FormatterServices.GetSerializableMembers(RealType, context);

            //
            // Build an array of member names that can be used for lookup.
            //
            
            string[] memberNames = new string[members.Length];
            for (int i = 0; i < members.Length; i++)
                memberNames[i] = members[i].Name;

            //
            // Build the data array and load it with the value of the
            // each member. A lookup is done each time to find the
            // corresponding index in the data and member arrays.
            //

            object[] datas = new object[members.Length];

            SerializationInfoEnumerator e = Info.GetEnumerator();
            while (e.MoveNext())
            {
                int i = Array.IndexOf(memberNames, e.Name);
                
                if (i != -1)
                    datas[i] = e.Value;
            }

            //
            // Finally, set the values of the real object's members. 
            //
            // NOTE: The documentation on PopulateObjectMembers says:
            // 
            //     If a matching surrogate for the object is found, then the 
            //     populating is handed off to that surrogate. Otherwise, if 
            //     the object implements ISerializable, the populating is 
            //     handed off to the object itself. If a matching surrogate 
            //     is not found, and the object does not implement the 
            //     ISerializable interface, then deserialization populates 
            //     each field listed in members, with the corresponding value 
            //     listed in data.
            //
            // Only the last bit seems to be true. That is, the function
            // populates each field listed in members with the corresponding 
            // value listed in data. Otherwise PopulateObjectMembers could not
            // be used as it is being done here!
            //

            FormatterServices.PopulateObjectMembers(realObject, members, datas);
            return realObject;
        }
    }
}
